home *** CD-ROM | disk | FTP | other *** search
/ Magnum One / Magnum One (Mid-American Digital) (Disc Manufacturing).iso / d12 / v10n11.arc / CHKPATH.C < prev    next >
C/C++ Source or Header  |  1991-05-10  |  59KB  |  1,649 lines

  1. /* Copyright Notice:
  2.     This program was written for PC Magazine by John Deurbrouck.
  3.     This program is a published, copyrighted work.
  4.     Copyright 1991. All rights reserved.
  5. */
  6. /* Compiling instructions:
  7. Borland C++ 2.0, in C mode:
  8.         for debug version
  9.     bcc -w -v -N -ml -f- -d -DDEBUG -edchkpath chkpath.c
  10.         for production version
  11.     bcc -Z -w -N- -ml -O -G- -f- -d chkpath.c
  12. Borland Turbo C++ 1.01, in C mode:
  13.         for debug version
  14.     tcc -w -v -N -ml -f- -d -DDEBUG -edchkpath chkpath.c
  15.         for production version
  16.     tcc -Z -w -N- -ml -O -G- -f- -d chkpath.c
  17. Microsoft C 6.00A:
  18.         for debug version
  19.     cl /DDEBUG /Zp1 /W4 /AL /Zi /Fedchkpath /F 2000 chkpath.c
  20.         for production version
  21.     cl /Zp1 /W4 /AL /Gs /F 2000 chkpath.c
  22. Microsoft Quick C 2.51:
  23.         for debug version
  24.     qcl /DDEBUG /Zp1 /W4 /AL /Zi /Fedchkpath /F 2000 chkpath.c
  25.         for production version
  26.     qcl /Zp1 /W4 /AL /Gs /F 2000 chkpath.c
  27. Zortech C++ 2.18, in C mode:
  28.         for debug version
  29.     ztc -ml -a1 -s -gl -gs -DDEBUG -odchkpath chkpath.c
  30.         for production version
  31.     ztc -ml -a1 chkpath.c -o
  32. I use Gimpel's PC Lint, and the code in this file lints clean in
  33.     Turbo C mode.  I don't have PC Lint set up for Microsoft or
  34.     Zortech code, so I didn't lint it in that mode.
  35. I used LZEXE to compact the executables. This product reduces the
  36.     amount of disk space an .EXE file occupies, at the price of a
  37.     fraction of a second's decompression time when the program runs.
  38. How did the size of the executables compare? Here's a list of the
  39. sizes I got for version 0.80:
  40.  
  41.                                                         LZEXE'd
  42.                             DEBUG      PRODUCTION     PRODUCTION
  43.  
  44.          Turbo C++ 2.0      34565           19258          12148
  45.  
  46.      Microsoft C 6.00A      31307           16549          10722
  47.  
  48. Microsoft Quick C 2.51      35721           19607          10998
  49.  
  50.       Zortech C++ 2.18      33804           21812          13502
  51.  
  52. */
  53. /* Version History
  54.     1.0 27Mar91   * Initial release.
  55. */
  56. /***
  57. Includes
  58. ***/
  59. #include<stdio.h>
  60. #include<stdlib.h>
  61. #include<string.h>
  62. #include<ctype.h>
  63. #include<dos.h>
  64. #include<time.h>
  65. #include<conio.h>
  66. #ifdef _MSC_VER
  67.     #include<sys\types.h>
  68.     #include<direct.h>
  69. #endif
  70. #ifdef __ZTC__
  71.     #include<sys\types.h>
  72.     #include<direct.h>
  73. #endif
  74. #include<sys\stat.h>
  75. #ifdef __TURBOC__
  76.     #include<dir.h>
  77. #endif
  78. /*
  79. **  We MUST have Zortech, Borland or Microsoft.
  80. **
  81. **  Here we ensure that one identifier or the other is present.
  82. */
  83. #ifndef __TURBOC__
  84.     #ifndef _MSC_VER
  85.         #ifndef __ZTC__
  86.             #error This requires Zortech, Turbo C, MSC, or Quick C
  87.         #endif
  88.     #endif
  89. #endif
  90. /***
  91. Defines
  92. ***/
  93. /*
  94. **  The EXIT() macro is always used to return to DOS, even if we have
  95. **  a successful completion and are returning from main().
  96. */
  97. #ifdef DEBUG
  98.     #define EXIT(arg) {trap();myquit(__FILE__,__LINE__,arg);}
  99. #else
  100.     #define EXIT(arg) myquit(arg);
  101. #endif
  102. /*
  103. **  COM, EXE and BAT are used to index into char *comexebat[]
  104. */
  105. #define COM                 0
  106. #define EXE                 1
  107. #define BAT                 2
  108. /*
  109. **  IS_ON is the character printed for executables on the path.
  110. **  IS_OFF is the character printed for executables off the path.
  111. **  _STR variants used to build header string
  112. */
  113. #define IS_ON             ' '
  114. #define IS_OFF            '-'
  115. #define IS_ON_STR         " "
  116. #define IS_OFF_STR        "-"
  117. /*
  118. **  MALLOC_BLOCK_SIZE determines how much space we ask malloc() for
  119. **  when we need space.  See the comments in malloc_nofree() for
  120. **  further details.
  121. */
  122. #define MALLOC_BLOCK_SIZE   8096
  123. /*
  124. **  SCREEN_LINES determines how often we stop for a keypress when the
  125. **  /p command line parameter is present.
  126. */
  127. #define SCREEN_LINES          22
  128. /*
  129. **  POSSIBLE_DRIVES is 26, the number of letters in the alphabet.
  130. */
  131. #define POSSIBLE_DRIVES       26
  132. /*
  133. **  mprintf is a function macro.  It simply replaces every mprintf
  134. **  with a call to check_screen_overflow() and then a printf. This
  135. **  can be DANGEROUS if you aren't aware of it, because this is not
  136. **  a single statement like it appears to be.  DON'T make this the
  137. **  object of an if statement, or you'll be sorry:
  138. **      if(0)mprintf("this is a very bad idea\n");
  139. **  translates to what you would normally type as:
  140. **      if(0)check_screen_overflow();
  141. **      printf("this is a very bad idea\n");
  142. **  which is not how you would normally read the source, since the
  143. **  message gets printed and not skipped as you would expect.
  144. **  BEWARE!!!
  145. */
  146. #define MPRINTF     check_screen_overflow();printf
  147. /*
  148. **  This program was originally written in Borland's Turbo C dialect.
  149. **  The following defines, along with the differences in which system
  150. **  include files had to be used, are all that is necessary to make
  151. **  the original code work under others. There are no conditional
  152. **  compilation statements in the code proper, except that Zortech's
  153. **  dos_getdrive works differently enough from Borland's and
  154. **  Microsoft's that a wrapper function, getdrive() had to be
  155. **  written.
  156. */
  157. #ifdef _MSC_VER
  158.     #define MAXDIR          _MAX_DIR
  159.     #define MAXPATH         _MAX_PATH
  160.     #define FA_RDONLY       _A_RDONLY
  161.     #define FA_HIDDEN       _A_HIDDEN
  162.     #define FA_SYSTEM       _A_SYSTEM
  163.     #define FA_LABEL        _A_VOLID
  164.     #define FA_DIREC        _A_SUBDIR
  165.     #define findfirst(name,blok,att)     _dos_findfirst(name,att,blok)
  166.     #define findnext                     _dos_findnext
  167.     #define ffblk                        find_t
  168.     #define ff_attrib                    attrib
  169.     #define ff_name                      name
  170.     #define ff_ftime                     wr_time
  171.     #define ff_fdate                     wr_date
  172.     #define ff_fsize                     size
  173.     /*
  174.     **  Disk drive access functions:
  175.     **
  176.     **  Note that while for Borland, A=0, B=1, C=2...,
  177.     **  for Microsoft, A=1, B=2, C=3...
  178.     **  Rather than have the dependencies in the code, I elected
  179.     **  to have them here.
  180.     */
  181.     #define getdisk()                    (_getdrive()-1)
  182.     #define setdisk(drive)               _chdrive((drive)+1)
  183. #endif
  184. #ifdef __ZTC__
  185.     #define MAXDIR          FILENAME_MAX
  186.     #define MAXPATH         FILENAME_MAX
  187.     #define findfirst(name,blok,att)     _dos_findfirst(name,att,blok)
  188.     #define findnext                     _dos_findnext
  189.     #define ffblk                        FIND
  190.     #define ff_attrib                    attribute
  191.     #define ff_name                      name
  192.     #define ff_ftime                     time
  193.     #define ff_fdate                     date
  194.     #define ff_fsize                     size
  195.     /*
  196.     **  Disk drive access functions:
  197.     **
  198.     **  Note that while for Borland, A=0, B=1, C=2...,
  199.     **  for Zortech, A=1, B=2, C=3...
  200.     **  Rather than have the dependencies in the code, I elected
  201.     **  to have them here.
  202.     */
  203.     #define setdisk(drive)          dos_setdrive((drive)+1,&numdrives)
  204. #endif
  205. /***
  206. Structures and typedefs and enums
  207. ***/
  208. /*
  209. **  This structure used to accumulate information about every
  210. **  directory to be searched.  The entries in this linked list
  211. **  are in search order.
  212. **
  213. **  Note that a SUBST can make a directory look as though it is
  214. **  repeated.  To avoid this, we must store information about both
  215. **  the true and substituted name.  To verify uniqueness, we need
  216. **  only verify uniqueness of truename.
  217. */
  218. typedef struct dir_queue_struct dir_q;
  219. struct dir_queue_struct{
  220.     char *dirname;
  221.     char *truename;
  222.     dir_q *next;
  223.     char on_search_path;     /* nonzero if on specified search path */
  224. };
  225. /*
  226. **  This structure is used to gather information about every
  227. **  executable file found. progname points to an ASCIIZ string which
  228. **  is just the base name. prog_directory points to an ASCIIZ string
  229. **  which is the drive and directory. next points to the next element
  230. **  in this linked list, and ext_type is COM, EXE or BAT. This was
  231. **  more compact than storing the extension in each progname. So,
  232. **  if you're storing information about C:\UTIL\CRUNCH.EXE,
  233. **      progname points to an ASCIIZ string, "CRUNCH"
  234. **      prog_directory points to "C:\UTIL\"
  235. **      ext_type will be equal to EXE, #defined above.
  236. **  Note also that to conserve memory, many prog_queue_struct entries
  237. **  may share one or more pointers. C:\UTIL\UNCRUNCH.EXE will have
  238. **  the same prog_directory pointer as the example above. And
  239. **  C:\UTIL\CRUNCH.BAT will have the same progname and prog_directory
  240. **  pointers.
  241. */
  242. struct prog_queue_struct{
  243.     char *progname;
  244.     char *truename;
  245.     dir_q *dir_queue_ptr;
  246.     struct prog_queue_struct *next;
  247.     char ext_type;
  248. };
  249. /*
  250. **  struct big_prog_queue_struct is the same as prog_queue_struct, but
  251. **  has space to store the file's date, time and size.  It's a bit
  252. **  larger, so you might run out of room. That's why there's the /n
  253. **  switch, which causes use of prog_queue_struct instead of
  254. **  big_prog_queue_struct.  This makes big_prog_queue_struct the
  255. **  default.
  256. */
  257. struct big_prog_queue_struct{
  258.     char *progname;
  259.     dir_q *dir_queue_ptr;
  260.     struct big_prog_queue_struct *next;
  261.     union time_splitter{
  262.         unsigned int ff_ftime;
  263.         struct time_bit_struct{
  264.             unsigned ft_tsec:5;
  265.             unsigned ft_min:6;
  266.             unsigned ft_hour:5;
  267.         }time_bits;
  268.     }ft;
  269.     union date_splitter{
  270.         unsigned int ff_fdate;
  271.         struct date_bit_struct{
  272.             unsigned fd_day:5;
  273.             unsigned fd_month:4;
  274.             unsigned fd_year:7;
  275.         }date_bits;
  276.     }fd;
  277.     long ff_fsize;
  278.     char ext_type;
  279. };
  280. /***
  281. Global Variables
  282. ***/
  283. char *comexebat[]={
  284.     {".COM"},
  285.     {".EXE"},
  286.     {".BAT"}
  287. };
  288. /*
  289. **  a nonzero entry in this array means the user wants that drive
  290. **  checked for nonpath programs.  set with /d= command-line switch.
  291. */
  292. int nonpath_drives[POSSIBLE_DRIVES];
  293. int show_nonpath=0;
  294. /*
  295. **  nonzero for dos_has_subst means the DOS version number is high
  296. **  enough that we ought to be accounting for SUBST possibility.
  297. */
  298. int dos_has_subst=0;
  299. /*
  300. **  this is the number of executable files found.
  301. */
  302. int num_dir_queue_entries=0;
  303. /*
  304. **  this is nonzero if the /a option is present on the command line.
  305. */
  306. int show_all_files=0;
  307. /*
  308. **  this is nonzero if we're accumulating date, time and size. (/n)
  309. */
  310. int show_details=1;
  311. /*
  312. **  this is nozero if the /p option is present on the command line
  313. */
  314. int screenful_pause=0;
  315. int lines_left_on_screen=SCREEN_LINES;
  316. /*
  317. **  this is nonzero if any directories were specified
  318. */
  319. int got_dir_specs=0;
  320. /*
  321. **  nonzero if we have printed at least one entry from prog_queue or
  322. **  big_prog_queue.
  323. */
  324. int showed_something=0;
  325. /*
  326. **  global_last_conflict is a pointer to a character string that
  327. **  gives the user-readable representation of a conflicting
  328. **  directory entry.  This can be problematic if the user has
  329. **  a SUBST directory on the command line, and a normal
  330. **  representation of the same thing.  In other words, if the user
  331. **  does the following DOS commands:
  332. **      C:\>subst b: c:\dos
  333. **      C:\>set path=c:\;c:\dos;b:\
  334. **  Then assume the user performs the following:
  335. **      C:\>chkpath /e=path
  336. **  obviously he has a conflict.  But a report simply stating that
  337. **  C:\DOS is specified twice may not be illuminating enough.  This
  338. **  global_last_conflict holds a pointer to the alias for the
  339. **  directory name being tested, so it can be reported by CHKPATH
  340. **  to make the exact problem clearer.
  341. */
  342. char *global_last_conflict=NULL;
  343. /*
  344. **  initialize linked lists as empty.
  345. */
  346. dir_q *dir_queue=(dir_q *)0;
  347. struct prog_queue_struct *prog_queue=(struct prog_queue_struct *)0;
  348. struct big_prog_queue_struct *big_prog_queue=
  349.     (struct big_prog_queue_struct *)0;
  350. #ifdef DEBUG
  351. /*
  352. **  these values are used to track memory usage in DEBUG mode only.
  353. */
  354. unsigned long total_malloc_memory=0UL;
  355. unsigned long total_memory_used=0UL;
  356. unsigned long total_memory_wasted=0UL;
  357. #endif
  358. /*
  359. **  nofiles_str and noconf_str are used when no output was
  360. **  generated (keys off showed_something).
  361. */
  362. char nofiles_str[]="No .COM, .EXE, or .BAT files in specified "
  363.     "directories.";
  364. char noconf_str[]="All .COM, .EXE and .BAT files in specified "
  365.     "directories are unique.";
  366. /*
  367. **  text array with copyright information
  368. */
  369. char *aacCopyrightArray[]={
  370.     {"CHKPATH 1.0 "
  371.     #ifdef DEBUG
  372.     "***Debug version***  "
  373.     #endif
  374.     "Copyright (c) 1991 Ziff Communications Co."},
  375.     {"PC Magazine * John Deurbrouck"},
  376.     {""}                             
  377. };
  378. /*
  379. **  text array with usage information. this is easier to maintain
  380. **  than a large series of printf() calls.
  381. */
  382. char *aacUsageArray[]={
  383.     {"Syntax:  CHKPATH [/a] [/d=drive(s)] [/n] [/p] [/s=dir1 ... "
  384.      "dirn] [/e=var]"},
  385.     {"              /a ALL. Lists all .COM, .EXE and .BAT files, "
  386.      "conflicting or not."},
  387.     {"     /d=drive(s) DRIVES. Searches all dirs on each listed "
  388.      "drive."},
  389.     {"              /n NO_DETAIL. No display of file date, time"
  390.      " and size."},
  391.     {"              /p PAUSE. Pauses after each screenful of report "
  392.      "lines."},
  393.     {"/s=dir1 ... dirn SPECIFIED. Specifies dir(s) to search."},
  394.     {"          /e=var ENVIRONMENT variable."},
  395.     {""}
  396. };
  397. /*
  398. **  Including exits.h twice and taking different data from it each
  399. **  time. This ensures that error messages and enumerated error
  400. **  values stay in sync.
  401. */
  402. char *aacErrorArray[]={
  403.     #ifdef EXITS
  404.         #undef EXITS
  405.     #endif
  406.     #define EXITS(name,string) {string},
  407.     #include"exits.h"
  408.     {""}
  409. };
  410. /*
  411. **  another text array
  412. */
  413. char *aacReptHdr[]={
  414.     {"-------- -----------------------------------------"
  415.      "-----------------------------\n"},
  416.     {"    NAME LOCATION                     (NAME printed only for "
  417.      "'winner')\n"},
  418.     {"         '" IS_OFF_STR "' replaces '" IS_ON_STR "' before LOC"
  419.      "ATION if not on specified search path.\n"}
  420. };
  421. /*
  422. **  the arguments defined here are the only arguments allowed to the
  423. **  EXIT() macro. This syncronizes readable error exit values (like
  424. **  ERROR_MALLOC) with the appropriate error messages.
  425. */
  426. typedef enum{
  427.     #ifdef EXITS
  428.         #undef EXITS
  429.     #endif
  430.     #define EXITS(name,string) name,
  431.                          /*lint -e537 yes, exits.h included twice OK*/
  432.                               /*lint -e726 extra comma on enum is OK*/
  433.     #include"exits.h"
  434.     ERROR_ERROR____      /* ANSI doesn't allow comma after last enum*/
  435.                                                         /*lint +e537*/
  436. }exit_code;                                             /*lint +e726*/
  437. #ifdef __TURBOC__
  438.     unsigned _stklen=0x2000;          /* set stack size for Turbo C */
  439.     /*
  440.     **  Note that Turbo C resets the stack here using the runtime,
  441.     **  rather than using the linker.  This means that when you look
  442.     **  at CHKPATH.EXE's header, you'll see the default 0x800 (2k)
  443.     **  stack size.  Step through the code and look at the CPU
  444.     **  registers to confirm to yourself that the stack is, in fact,
  445.     **  set to the desired size...
  446.     */
  447. #endif
  448. #ifdef __ZTC__
  449.     int _stack=0x2000;                /* set stack size for Turbo C */
  450.     /*
  451.     **  Note that Zortech C resets the stack here using the runtime,
  452.     **  rather than using the linker.
  453.     */
  454. #endif
  455. #ifdef __ZTC__
  456.     /*
  457.     **  Zortech needs another parameter for its setdisk() equivalent.
  458.     */
  459.     unsigned numdrives;
  460. #endif
  461. /***
  462. Function Prototypes
  463. ***/
  464. void main(int argc,char *argv[]);
  465. void show_copyright(void);
  466. void show_usage(void);
  467. #ifdef DEBUG
  468.     void trap(void);
  469.     void myquit(char *file,int line,exit_code arg);
  470. #else
  471.     void myquit(exit_code arg);
  472. #endif
  473. void process_cmd_arg(char *ptr);
  474. int set_nonpath_drives(char *ptr);
  475. void add_nonpath_directories(void);
  476. void add_dir_and_recurse(char *dir);
  477. int normalize_path_name(char *inpath,char *outpath,int buflen);
  478. dir_q *add_dir_queue(char *newdir,int on_path);
  479. dir_q *get_dir_queue_space(char *dir,char *true,int on_path);
  480. void scan_dirs(void);
  481. void find_ext_in_dir(dir_q *directory,int extension);
  482. void add_prog_queue(dir_q *directory,char *name,int extension);
  483. void add_big_prog_queue(dir_q *directory,struct ffblk *ff,
  484.  int extension);
  485. void show_results(void);
  486. void show_big_results(void);
  487. void make_big_output(char *out_array,char *label,struct
  488.  big_prog_queue_struct *ptr);
  489. void check_screen_overflow(void);
  490. void *malloc_nofree(size_t size);
  491. int cur_drive_is_subst(void);
  492. int get_truename(char *true,char *original);
  493. int does_dos_have_subst(void);
  494. #ifdef __ZTC__
  495.     int getdisk(void);
  496. #endif
  497. /***
  498. Lint setup
  499. ***/
  500.               /*lint -e720 boolean test of assignment is intentional*/
  501.                  /*lint -esym(534,int86x) ok to ignore return value */
  502. /***
  503. Function Definitions
  504. ***/
  505. void main(int argc,char *argv[]){
  506. /** void main(int argc,char *argv[]);
  507.         calls process_cmd_arg() with each command line argument, then
  508.     calls scan_dirs() and show_results() or show_big_results()
  509. **/
  510.     show_copyright();
  511.     argc--;                                /* scan off program name */
  512.     argv++;
  513.     memset(nonpath_drives,0,sizeof(nonpath_drives)); /* assure all 0*/
  514.     dos_has_subst=does_dos_have_subst();
  515.     if(!argc)EXIT(ERROR_USAGE)                    /* no args at all */
  516.     while(argc--){                         /* process cmd line args */
  517.         process_cmd_arg(*argv);
  518.         argv++;
  519.     }
  520.     if(num_dir_queue_entries)got_dir_specs=1;
  521.     add_nonpath_directories();
  522.     if(!num_dir_queue_entries)EXIT(ERROR_NODIRS)
  523.     scan_dirs();
  524.     if(show_details)show_big_results();
  525.     else show_results();
  526.     if(!showed_something){
  527.         /*
  528.         **  pick correct message based on prog_queue or big_prog_queue
  529.         */
  530.         MPRINTF("%s\n",
  531.         !prog_queue&&!big_prog_queue?nofiles_str:noconf_str);
  532.     }
  533.     EXIT(ERROR_SUCCESS);
  534. }
  535. void show_copyright(void){
  536. /** void show_copyright(void);
  537.         puts the copyright notice to stderr
  538. **/
  539.     char **t=aacCopyrightArray;
  540.     while(**t)fprintf(stderr,"%s\n",*t++);
  541. }
  542. void show_usage(void){
  543. /** void show_usage(void);
  544.         puts the usage information to stderr
  545. **/
  546.     char **t=aacUsageArray;
  547.     while(**t)fprintf(stderr,"%s\n",*t++);
  548. }
  549. #ifdef DEBUG
  550. void trap(void){
  551. /** void trap(void);
  552.         Does nothing.  Just here to provide for debugger trapping
  553.     before error goes out of scope.  If you make changes and have
  554.     problems, put a breakpoint on the closing brace of this function.
  555.     When a fatal problem occurs, you can then step out of this
  556.     function back to the scope in which the EXIT() macro occurs.
  557. **/
  558. }
  559. #endif
  560. #ifdef DEBUG
  561. void myquit(char *file,int line,exit_code arg){
  562. #else
  563. void myquit(exit_code arg){
  564. #endif
  565. /** void myquit(exit_code arg);
  566.     DEBUG version has following prototype:
  567.     void myquit(char *file,int line,exit_code arg);
  568.         Shows the appropriate exit string, then bails out with exit()
  569.     to return errorlevel.
  570.         Also displays aacUsageArray if arg==ERROR_USAGE.
  571. **/
  572.     if(arg==ERROR_USAGE)show_usage();
  573.     #ifndef DEBUG
  574.     else{
  575.         if(arg!=ERROR_SUCCESS){
  576.             fprintf(stderr,"EXITING: %s\n",aacErrorArray[(int)arg]);
  577.         }
  578.     }
  579.     #endif
  580.     #ifdef DEBUG
  581.     fprintf(stderr,"MEMORY: malloc=%lu, used=%lu, wasted=%lu\n",
  582.     total_malloc_memory,total_memory_used,total_memory_wasted);
  583.     fprintf(stderr,"EXITING:%s(%d): %s\n",
  584.     file,line,aacErrorArray[(int)arg]);
  585.     #endif
  586.     exit((int)arg);
  587. }
  588. void process_cmd_arg(char *ptr){
  589. /** void process_cmd_arg(char *ptr);
  590.         takes a string that may be a directory name or the name of an
  591.     environment variable. Figures out which one, then adds all
  592.     possible directory names to the dir_queue, incrementing
  593.     num_dir_queue_entries as it goes. Calls add_dir_queue() to do
  594.     actual update. Converts all input to uppercase.
  595.         Since we don't actually scan any directory until all the
  596.     command line arguments have been processed, we don't have to
  597.     look ahead for the /d parameter.
  598. **/
  599.     char *work,input_buffer[512],full_path[MAXDIR+3],*env_ptr;
  600.     int was_environment_variable=0;
  601.     if(!*ptr)return;
  602.     /*
  603.     **  first we coerce to uppercase
  604.     */
  605.     work=ptr;
  606.     while(*work){
  607.         *work=(char)toupper(*work);
  608.         work++;
  609.     }
  610.     /*
  611.     **  now check for command line arguments
  612.     */
  613.     if(!strcmp(ptr,"/A")){            /* check for cmd line /a flag */
  614.         show_all_files=1;
  615.         return;
  616.     }
  617.     if(!strcmp(ptr,"/N")){            /* check for cmd line /n flag */
  618.         show_details=0;
  619.         return;
  620.     }
  621.     if(!strcmp(ptr,"/P")){            /* check for cmd line /p flag */
  622.         screenful_pause=1;
  623.         return;
  624.     }
  625.     if(!memcmp(ptr,"/D=",3)){         /* check for cmd line /d flag */
  626.         if(!set_nonpath_drives(&ptr[3]))EXIT(ERROR_BAD_DRIV)
  627.         show_nonpath=1;
  628.         return;
  629.     }
  630.     if(!memcmp(ptr,"/E=",3)){               /* environment variable */
  631.         was_environment_variable=1;
  632.         env_ptr=&ptr[3];
  633.         ptr=getenv(&ptr[3]);
  634.         if(!ptr||!*ptr){
  635.             fprintf(stderr,"Specified environment variable '%s'\n",
  636.             env_ptr);
  637.             EXIT(ERROR_ENVIRON);
  638.         }
  639.     }
  640.     if(!memcmp(ptr,"/S=",3)){                /* specified directory */
  641.         ptr+=3;                    /* throw away switch, not needed */
  642.     }
  643.     /*
  644.     **  what we have is a semicolon-separated directory list, and we
  645.     **  want to separate it into a series of usable directory names.
  646.     **  The process is pictured with the input_buffer= comments below.
  647.     */
  648.     input_buffer[0]=0;
  649.     strcpy(&input_buffer[1],ptr);   /* input_buffer=\0DIR;DIR;DIR\0 */
  650.     work=&input_buffer[1];
  651.     /*
  652.     **  now we replace all semicolons with nulls, and add an extra
  653.     **  null on the end (for later loop control)
  654.     */
  655.     for(;;){
  656.         switch(*work){
  657.             case 0:
  658.                 work[1]=0;
  659.                 break;
  660.             case ';':
  661.                 *work=0;
  662.                 work++;
  663.                 continue;
  664.             default:
  665.                 work++;
  666.                 continue;
  667.         }
  668.         break;
  669.     }                           /* input_buffer=\0DIR\0DIR\0DIR\0\0 */
  670.     /*
  671.     **  now we have a series of directories, all null-separated.
  672.     **  let's cycle through them, processing them one at a time
  673.     **  until there are none left.
  674.     */
  675.     work=input_buffer;
  676.     while(work[1]){
  677.         int is_relative,is_multi,is_nonexistent;
  678.         is_multi=is_nonexistent=0;
  679.         work++;
  680.         is_relative=(work[1]!=':'||work[2]!='\\')?1:0;
  681.         if(normalize_path_name(work,full_path,MAXDIR+3)){
  682.             if(add_dir_queue(full_path,1))num_dir_queue_entries++;
  683.             else is_multi=1;
  684.         }
  685.         else is_nonexistent=1;
  686.         if(is_relative||is_multi||is_nonexistent){
  687.             if(was_environment_variable)printf("ENV VAR '%s', ",
  688.             env_ptr);
  689.             printf("Directory %s",work);
  690.             if(is_relative)printf(", relative");
  691.             if(is_multi){
  692.                 printf(", specified more than once");
  693.                 if(global_last_conflict){
  694.                     printf(" (%s)",global_last_conflict);
  695.                     global_last_conflict=NULL;
  696.                 }
  697.             }
  698.             if(is_nonexistent)printf(", could not be located");
  699.             MPRINTF("\n");
  700.         }
  701.         while(*work)work++;    /* processed, so scan off this entry */
  702.     }
  703. }                                       
  704. int set_nonpath_drives(char *ptr){
  705. /** int set_nonpath_drives(char *ptr);
  706.         Goes through the character string pointed to by ptr.
  707.         Expects a string in the following form:
  708.             ltr_a[-ltr_b]...
  709.         where ltr_a is any letter, and ltr_b is any letter higher
  710.         than ltr_a in the alphabet.  it is expected that all
  711.         letters are uppercase.
  712.         returns 0 for failure
  713.                 1 for success
  714.         primary function is to set nonpath_drives entries to nonzero
  715.             for every letter specified
  716.         yes, the letters at each end of the range get set twice, but
  717.             the runtime cost is so tiny it's not worth any code space
  718.             to fix
  719.         note that we allow the user to enter colons, for a string
  720.             that looks like /d=c:-e:g:
  721.         last is set to 120 so if a dash ('-') is encountered before
  722.             any drive letters, we'll fail as we should
  723. **/
  724.     int count,last=120;
  725.     if(!isupper((int)*ptr))return 0;
  726.     for(;;){
  727.         /*
  728.         **  we don't really use the colons, so just discard them
  729.         */
  730.         if(*ptr==':'){
  731.             ptr++;
  732.             continue;
  733.         }
  734.         if(*ptr=='-'){
  735.             if(!(last<ptr[1]&&
  736.             isupper(last)&&isupper((int)ptr[1]))){
  737.                 return 0;
  738.             }
  739.             for(count=last;count<(int)ptr[1];count++){
  740.                 nonpath_drives[count-'A']=1;
  741.             }
  742.             ptr++;
  743.             continue;
  744.         }
  745.         last=(int)*ptr;
  746.         if(!isupper(last))return 1;
  747.         nonpath_drives[last-'A']=1;
  748.         ptr++;
  749.     }
  750. }
  751. void add_nonpath_directories(void){
  752. /** void add_nonpath_directories(void);
  753.         Goes through all drives from A: to Z:, adding all directories
  754.             on each to the dir_queue.  Checks only if user wanted
  755.             that particular drive searched.
  756.         For DOS 3.1+, goes through twice.  The first time, only
  757.             processes SUBST drives.  The second time, only processes
  758.             non-SUBST drives.  This is so the user gets the most
  759.             familiar representation.
  760. **/
  761.     /*  Pseudocode:
  762.         save current drive
  763.         for candidate=A: to Z:
  764.             if (user wants, and change to candidate drive works)
  765.                 call add_dir_and_recurse() with '\'
  766.             endif
  767.         end for
  768.         restore current drive
  769.     */
  770.     int old_drive,candidate,big_loop;
  771.     char root_dir[4];
  772.     old_drive=getdisk();                      /* save current drive */
  773.     root_dir[1]=':';
  774.     root_dir[2]='\\';
  775.     root_dir[3]=0;
  776.     for(big_loop=0;big_loop<2;big_loop++){
  777.         for(candidate=0;candidate<POSSIBLE_DRIVES;candidate++){
  778.             if(!nonpath_drives[candidate])continue;
  779.             setdisk(candidate);
  780.             if(dos_has_subst&&(big_loop==cur_drive_is_subst())){
  781.                 continue;
  782.             }
  783.             if(getdisk()!=(candidate)){
  784.                 MPRINTF("Could not change to drive %c:\n",
  785.                 'A'+candidate);
  786.                 continue;                         /* reject invalid */
  787.             }
  788.             root_dir[0]=(char)(candidate+'A');
  789.             add_dir_and_recurse(root_dir);
  790.         }
  791.         if(!dos_has_subst)break;
  792.     }
  793.     setdisk(old_drive);                    /* restore current drive */
  794.     if(getdisk()!=old_drive)EXIT(ERROR_DOS)  /* drive restore error */
  795. }
  796. void add_dir_and_recurse(char *dir){
  797. /** void add_dir_and_recurse(char *dir);
  798.     this takes a directory of the form C:\, C:\DOS\, etc.
  799.     it tries to add the directory to the dir_queue as a nonpath
  800.         directory, then calls itself with each directory it contains.
  801. **/
  802.     /*  Pseudocode
  803.         try to add self to dir queue as nonpath member
  804.         if an entry in directory
  805.             if it's a directory
  806.                 add_dir_and_recurse() it
  807.             endif
  808.             while further entries in directory
  809.                 if one is a directory
  810.                     add_dir_and_recurse() it
  811.                 endif
  812.             end while
  813.         endif
  814.     */
  815.     static char buffer[MAXPATH+15]; /* static to reduce stack space */
  816.     struct ffblk ffblock;
  817.     char *null_past_original;
  818.     if(add_dir_queue(dir,0))num_dir_queue_entries++;
  819.     strcpy(buffer,dir);
  820.     null_past_original=buffer;
  821.     while(*null_past_original)null_past_original++;
  822.     strcpy(null_past_original,"*.*");
  823.     if(!findfirst(buffer,&ffblock,
  824.     FA_RDONLY|FA_HIDDEN|FA_SYSTEM|FA_DIREC)){
  825.         if(ffblock.ff_attrib&FA_DIREC&&ffblock.ff_name[0]!='.'){
  826.             *null_past_original=0;
  827.             strcat(buffer,ffblock.ff_name);
  828.             strcat(buffer,"\\");
  829.             add_dir_and_recurse(buffer);
  830.         }
  831.         while(!findnext(&ffblock)){
  832.             if(ffblock.ff_attrib&FA_DIREC&&ffblock.ff_name[0]!='.'){
  833.                 *null_past_original=0;
  834.                 strcat(buffer,ffblock.ff_name);
  835.                 strcat(buffer,"\\");
  836.                 add_dir_and_recurse(buffer);
  837.             }
  838.         }
  839.     }
  840.  
  841. }
  842. int normalize_path_name(char *inpath,char *outpath,int buflen){
  843. /** int normalize_path_name(char *inpath,char *outpath,int buflen);
  844.         Takes a pointer to a path, and returns 1 for success, 0 for
  845.             failure
  846.         Failure is possible in the case of either an invalid drive or
  847.             a nonexistent directory
  848.         In the case of success, copies the full path name, which will
  849.             always have a trailing '\\' character.
  850.         In the case of failure, the contents of *outpath are
  851.             unreliable
  852.         We actually use DOS to normalize these path names. We'd
  853.             access DOS nearly as much if we parsed it all ourselves,
  854.             and the code would be much more complex. This procedure
  855.             gets called only once per directory, and that's at most
  856.             20 or so times. Total runtime consumed will be under
  857.             one second, so increasing the complexity was not
  858.             worthwhile.
  859. **/
  860.     /* Pseudocode
  861.         if a drive is specified
  862.             save old drive
  863.             attempt to change to new drive
  864.             if fail return fail endif
  865.         endif
  866.         if a path is specified
  867.             save old path
  868.             attempt to change to new path
  869.             if fail
  870.                 if changed drive
  871.                     change back to old drive
  872.                     if fail quit program
  873.                 endif
  874.                 return fail
  875.             endif
  876.         endif
  877.         get current drive and directory into *outpath
  878.         append '\\' to *outpath if necessary
  879.         if path was specified
  880.             change back to old path
  881.             if fail quit program
  882.         endif
  883.         if drive was specified
  884.             change back to old drive
  885.             if fail quit program
  886.         endif
  887.         return success
  888.     */
  889.     int old_drive,drive_changed=0,path_changed=0;
  890.     char *startpath=inpath,*add_slash=outpath;
  891.     char old_path[MAXDIR+2];
  892.     /*
  893.     **  first we need to be logged onto the same drive as the
  894.     **  target file.
  895.     */
  896.     if(inpath[1]==':'){
  897.         startpath+=2;                  /* point at path after drive */
  898.         old_drive=getdisk();
  899.         drive_changed=1;
  900.         setdisk((int)*inpath-'A');
  901.         if(getdisk()!=(int)*inpath-'A'){
  902.             setdisk(old_drive);
  903.             if(getdisk()!=old_drive)EXIT(ERROR_DOS)
  904.             return 0;
  905.         }
  906.     }
  907.     /*
  908.     **  now we change to the specified directory if there is one
  909.     */
  910.     if(*startpath){
  911.         path_changed=1;
  912.         /*
  913.         **  save old directory
  914.         */
  915.         if(getcwd(old_path,sizeof(old_path)-1)!=old_path)
  916.             EXIT(ERROR_DOS)
  917.         /*
  918.         **  change to new directory
  919.         */
  920.         if(chdir(startpath)){
  921.             if(drive_changed){
  922.                 setdisk(old_drive);
  923.                 if(getdisk()!=old_drive)EXIT(ERROR_DOS)
  924.             }
  925.             return 0;
  926.         }
  927.     }
  928.     /*
  929.     **  whether we changed or not, this getcwd() call will get us the
  930.     **  full directory specification of the potentially relative
  931.     **  specification handed to us.
  932.     */
  933.     if(!getcwd(outpath,buflen-1))EXIT(ERROR_DOS)
  934.     while(add_slash[1])add_slash++;           /* point at last char */
  935.     /*
  936.     **  make sure return string has trailing backslash (\)
  937.     */
  938.     if(*add_slash!='\\'){
  939.         add_slash[1]='\\';                         /* add backslash */
  940.         add_slash[2]=0;                       /* add null delimiter */
  941.     }
  942.     /*
  943.     **  clean up and return
  944.     */
  945.     if(path_changed){                   /* changed path, so restore */
  946.         if(chdir(&old_path[2]))
  947.             EXIT(ERROR_DOS)                     /* path restore err */
  948.     }
  949.     if(drive_changed){
  950.         setdisk(old_drive);
  951.         if(getdisk()!=old_drive)EXIT(ERROR_DOS) /* drive restore err*/
  952.     }
  953.     return 1;
  954. }
  955. dir_q *add_dir_queue(char *newdir,int on_path){
  956. /** dir_q *add_dir_queue(char *newdir,int on_path);
  957.         tries to add newdir to dir_queue
  958.         copies *newdir to a newly allocated buffer, and allocates
  959.             space for a dir_queue entry as well
  960.         returns newly allocated pointer for success
  961.                 NULL for failure due to newdir already being present
  962. **/
  963.     /* Pseudocode
  964.         get truename for this path into true_name[]
  965.         if truename is different
  966.             point true_name_ptr at true_name[]
  967.         otherwise
  968.             point true_name_ptr at newdir
  969.         endif
  970.         if no entries
  971.             initialize dir_queue to point to the get_dir_queue_space
  972.              result
  973.             get space for the true name if it's different from newdir,
  974.              and insert it into node
  975.             return 1
  976.         endif
  977.         current_pointer=dir_queue
  978.         do forever
  979.             if new entry duplicates current_pointer
  980.                 also report name in global_last_conflict if one or the
  981.                  other was a SUBST result
  982.                 return 0
  983.             endif
  984.             if current_pointer -> next
  985.                 current_pointer=current_pointer->next
  986.             else
  987.                 add new entry at end
  988.                 return 1
  989.             endif
  990.         end do
  991.     */
  992.     char true_name[MAXPATH+15],*true_name_ptr;
  993.     dir_q *ptr,*current_pointer;
  994.     true_name_ptr=get_truename(true_name,newdir)?true_name:newdir;
  995.     if(!dir_queue){
  996.         dir_queue=get_dir_queue_space(newdir,true_name_ptr,on_path);
  997.         return dir_queue;
  998.     }
  999.     current_pointer=dir_queue;
  1000.     for(;;){
  1001.         if(!strcmp(true_name_ptr,current_pointer->truename)){
  1002.             if(strcmp(newdir,current_pointer->dirname)){
  1003.                 global_last_conflict=current_pointer->dirname;
  1004.             }
  1005.             else{
  1006.                 if(true_name_ptr!=newdir){
  1007.                     global_last_conflict=true_name_ptr;
  1008.                 }
  1009.             }
  1010.             return (dir_q *)0;
  1011.         }
  1012.         if(current_pointer->next){
  1013.             current_pointer=current_pointer->next;
  1014.         }
  1015.         else{
  1016.             ptr=get_dir_queue_space(newdir,true_name_ptr,on_path);
  1017.             current_pointer->next=ptr;
  1018.             return ptr;
  1019.         }
  1020.     }
  1021. }
  1022. dir_q *get_dir_queue_space(char *dir,char *true,int on_path){
  1023. /** dir_q *get_dir_queue_space(char *dir,char *true,int on_path);
  1024.         allocates room for dirname and an dir_queue entry, and for
  1025.             the true string if it's not equal to dir
  1026.         copies data into new space and points the dir_queue->dirname
  1027.             at the new space, sets the dir_queue->next to NULL,then
  1028.             returns the dir_q pointer
  1029.         does EXIT(ERROR_MALLOC) if failure
  1030. **/ 
  1031.     dir_q *ptr;
  1032.     if(!(ptr=malloc_nofree(sizeof(dir_q)))){
  1033.         EXIT(ERROR_MALLOC)
  1034.     }
  1035.     if(!(ptr->dirname=malloc_nofree(strlen(dir)+1)))EXIT(ERROR_MALLOC)
  1036.     strcpy(ptr->dirname,dir);
  1037.     if(dir!=true){
  1038.         if(!(ptr->truename=malloc_nofree(strlen(true)+1)))
  1039.             EXIT(ERROR_MALLOC)
  1040.         strcpy(ptr->truename,true);
  1041.     }
  1042.     else ptr->truename=ptr->dirname;
  1043.     ptr->on_search_path=(char)(on_path?1:0);
  1044.     ptr->next=(dir_q *)0;
  1045.     return ptr;
  1046. }
  1047. void scan_dirs(void){
  1048. /** void scan_dirs(void);
  1049.         goes through each entry in dir_queue, calling
  1050.             find_ext_in_dir() for COM, EXE and BAT
  1051. **/
  1052.     dir_q *ptr;
  1053.     ptr=dir_queue;
  1054.     while(ptr){
  1055.         if(ptr->dirname){
  1056.             find_ext_in_dir(ptr,COM);
  1057.             find_ext_in_dir(ptr,EXE);
  1058.             find_ext_in_dir(ptr,BAT);
  1059.         }
  1060.         ptr=ptr->next;
  1061.     }
  1062. }
  1063. void find_ext_in_dir(dir_q *directory,int extension){
  1064. /** void find_ext_in_dir(dir_q *directory,int extension);
  1065.         finds all occurances of files in the given directory with the
  1066.             given extension, and calls add_prog_queue with each one
  1067.             to add it to prog_queue
  1068. **/
  1069.     char target[MAXPATH+15];
  1070.     int done_looking;
  1071.     struct ffblk ffblock;
  1072.     strcpy(target,directory->dirname);
  1073.     strcat(target,"*");
  1074.     strcat(target,comexebat[extension]);
  1075.     if(!(done_looking=findfirst(target,&ffblock,
  1076.     FA_RDONLY|FA_HIDDEN|FA_SYSTEM))){
  1077.         while(!done_looking){
  1078.             if(!(ffblock.ff_attrib&(FA_LABEL|FA_DIREC))){
  1079.                 if(show_details){
  1080.                     add_big_prog_queue(directory,&ffblock,extension);
  1081.                 }
  1082.                 else{
  1083.                     add_prog_queue(directory,ffblock.ff_name,
  1084.                     extension);
  1085.                 }
  1086.             }
  1087.             done_looking=findnext(&ffblock);
  1088.         }
  1089.     }
  1090. }
  1091. void add_prog_queue(dir_q *directory,char *name,int extension){
  1092. /** void add_prog_queue(dir_q *directory,char *name,int extension);
  1093.         adds the specified program to the queue. no effort made to
  1094.             eliminate redundancy, since we have already eliminated
  1095.             duplicate directories
  1096.         will EXIT(ERROR_MALLOC) if can't get memory
  1097. **/
  1098.     /* pseudocode
  1099.         chop extension, if any, off name
  1100.         we add to prog_queue for every invocation, so get space
  1101.         copy all data but progname into new prog_queue entry,next=NULL
  1102.         if no previous entries or goes before first entry
  1103.             allocate space for progname
  1104.             point prog_queue to new entry
  1105.             new entry -> next = old prog_queue
  1106.             return
  1107.         endif
  1108.         old=current=prog_queue
  1109.         do forever
  1110.             switch on comparison, name to current->name
  1111.                 name EQUALS current->name
  1112.                     copy current->name pointer into ptr->name
  1113.                     while (current->next) and 
  1114.                     (current->next->name==ptr->name)
  1115.                         current=current->next
  1116.                     end while
  1117.                     ptr->next=current->next
  1118.                     current->next=ptr
  1119.                     return
  1120.                 name GREATER THAN current->name
  1121.                     old=current
  1122.                     if there is a next entry
  1123.                         current=current->next
  1124.                         continue the do loop
  1125.                     endif
  1126.                     fall through to LESS THAN case
  1127.                 name LESS THAN current->name
  1128.                     get string space, copy, ptr->progname=address
  1129.                     ptr->next=old->next
  1130.                     old->next=ptr
  1131.                     return
  1132.             end switch
  1133.         end do
  1134.     */
  1135.     char *temp;
  1136.     struct prog_queue_struct *ptr,*current,*old;
  1137.     int strcmp_result;
  1138.     temp=name;                           /* chop extension off name */
  1139.     while(*temp){
  1140.         if(*temp=='.')*temp=0;
  1141.         else temp++;
  1142.     }
  1143.     if((ptr=malloc_nofree(sizeof(struct prog_queue_struct)))==NULL){
  1144.         EXIT(ERROR_MALLOC)
  1145.     }                                                  /* get space */
  1146.     ptr->progname=(char *)0;                           /* copy data */
  1147.     ptr->ext_type=(char)extension;
  1148.     ptr->dir_queue_ptr=directory;
  1149.     if(!prog_queue||strcmp(name,prog_queue->progname)<0){
  1150.         ptr->progname=malloc_nofree(strlen(name)+1);
  1151.         strcpy(ptr->progname,name);
  1152.         ptr->next=prog_queue;
  1153.         prog_queue=ptr;
  1154.         return;
  1155.     }
  1156.     old=current=prog_queue;
  1157.     for(;;){
  1158.         strcmp_result=strcmp(name,current->progname);
  1159.         if(strcmp_result>0)strcmp_result=1;
  1160.         else if(strcmp_result<0)strcmp_result=-1;
  1161.         switch(strcmp_result){
  1162.             case  0:                   /* name EQUALS current->name */
  1163.                 ptr->progname=current->progname;
  1164.                 while(current->next&&
  1165.                 current->next->progname==ptr->progname){
  1166.                     current=current->next;
  1167.                 }
  1168.                 ptr->next=current->next;
  1169.                 current->next=ptr;
  1170.                 return;
  1171.             case  1:             /* name GREATER THAN current->name */
  1172.                 old=current;
  1173.                 if(current->next){
  1174.                     current=current->next;
  1175.                     continue;
  1176.                 }                 /* fall through to LESS THAN case */
  1177.             case -1:                /* name LESS THAN current->name */
  1178.                 ptr->progname=malloc_nofree(strlen(name)+1);
  1179.                 strcpy(ptr->progname,name);
  1180.                 ptr->next=old->next;
  1181.                 old->next=ptr;
  1182.                 return;            /*lint -e744 no default on switch*/
  1183.         }                                               /*lint +e744*/
  1184.     }
  1185. }
  1186. void add_big_prog_queue(dir_q *directory,struct ffblk *ff,
  1187. int extension){
  1188. /** void add_big_prog_queue(dir_q *directory,struct ffblk *ff,
  1189.     int extension);
  1190.         adds the specified program to the queue. no effort made to
  1191.             eliminate redundancy, since we have already eliminated
  1192.             duplicate directories
  1193.         will EXIT(ERROR_MALLOC) if can't get memory
  1194.         this function is exactly like add_prog_queue(), except that
  1195.             it collects the extra information and addresses
  1196.             big_prog_queue instead of prog_queue
  1197. **/
  1198.     /* pseudocode
  1199.         chop extension, if any, off name
  1200.         we add to big_prog_queue for every invocation, so get space
  1201.         copy all data but progname into new big_prog_queue entry,
  1202.          next=NULL
  1203.         if no previous entries or goes before first entry
  1204.             allocate space for progname
  1205.             point big_prog_queue to new entry
  1206.             new entry -> next = old big_prog_queue
  1207.             return
  1208.         endif
  1209.         old=current=big_prog_queue
  1210.         do forever
  1211.             switch on comparison, name to current->name
  1212.                 name EQUALS current->name
  1213.                     copy current->name pointer into ptr->name
  1214.                     while (current->next) and 
  1215.                     (current->next->name==ptr->name)
  1216.                         current=current->next
  1217.                     end while
  1218.                     ptr->next=current->next
  1219.                     current->next=ptr
  1220.                     return
  1221.                 name GREATER THAN current->name
  1222.                     old=current
  1223.                     if there is a next entry
  1224.                         current=current->next
  1225.                         continue the do loop
  1226.                     endif
  1227.                     fall through to LESS THAN case
  1228.                 name LESS THAN current->name
  1229.                     get string space, copy, ptr->progname = address
  1230.                     ptr->next=old->next
  1231.                     old->next=ptr
  1232.                     return
  1233.             end switch
  1234.         end do
  1235.     */
  1236.     char *temp,*name=ff->ff_name;
  1237.     struct big_prog_queue_struct *ptr,*current,*old;
  1238.     int strcmp_result;
  1239.     temp=ff->ff_name;                    /* chop extension off name */
  1240.     while(*temp){
  1241.         if(*temp=='.')*temp=0;
  1242.         else temp++;
  1243.     }
  1244.     if((ptr=malloc_nofree(sizeof(struct big_prog_queue_struct)))
  1245.     ==NULL){
  1246.         EXIT(ERROR_MALLOC)
  1247.     }                                                  /* get space */
  1248.     ptr->progname=(char *)0;                           /* copy data */
  1249.     ptr->ext_type=(char)extension;
  1250.     ptr->dir_queue_ptr=directory;
  1251.     ptr->ft.ff_ftime=ff->ff_ftime;
  1252.     ptr->fd.ff_fdate=ff->ff_fdate;
  1253.     ptr->ff_fsize=ff->ff_fsize;
  1254.     if(!big_prog_queue||strcmp(name,big_prog_queue->progname)<0){
  1255.         ptr->progname=malloc_nofree(strlen(name)+1);
  1256.         strcpy(ptr->progname,name);
  1257.         ptr->next=big_prog_queue;
  1258.         big_prog_queue=ptr;
  1259.         return;
  1260.     }
  1261.     old=current=big_prog_queue;
  1262.     for(;;){
  1263.         strcmp_result=strcmp(name,current->progname);
  1264.         if(strcmp_result>0)strcmp_result=1;
  1265.         else if(strcmp_result<0)strcmp_result=-1;
  1266.         switch(strcmp_result){
  1267.             case  0:                   /* name EQUALS current->name */
  1268.                 ptr->progname=current->progname;
  1269.                 while(current->next&¤t->next->progname
  1270.                 ==ptr->progname){
  1271.                     current=current->next;
  1272.                 }
  1273.                 ptr->next=current->next;
  1274.                 current->next=ptr;
  1275.                 return;
  1276.             case  1:             /* name GREATER THAN current->name */
  1277.                 old=current;
  1278.                 if(current->next){
  1279.                     current=current->next;
  1280.                     continue;
  1281.                 }                 /* fall through to LESS THAN case */
  1282.             case -1:                /* name LESS THAN current->name */
  1283.                 ptr->progname=malloc_nofree(strlen(name)+1);
  1284.                 strcpy(ptr->progname,name);
  1285.                 ptr->next=old->next;
  1286.                 old->next=ptr;
  1287.                 return;            /*lint -e744 no default on switch*/
  1288.         }                                               /*lint +e744*/
  1289.     }
  1290. }
  1291. void show_results(void){
  1292. /** void show_results(void);
  1293.         If show_all_files is zero
  1294.             Displays the the prog_queue entries, if any conflicts.
  1295.             If not, displays a 'no conflicts found' message.
  1296.         else
  1297.             displays all prog_queue entries, if there are any
  1298.             if not, displays a 'no files found' message.
  1299.         endif
  1300. **/
  1301.     /* pseudocode
  1302.         if no entries
  1303.             return
  1304.         endif
  1305.         do forever
  1306.             if off end of list
  1307.                 quit
  1308.             endif
  1309.             if (showing all files) or
  1310.             (this progname matches next progname)
  1311.                 if showed_something is zero
  1312.                     showed_something=1
  1313.                     print header
  1314.                 endif
  1315.                 print this entry with its name at the far left of line
  1316.                 do forever
  1317.                     if off end of list
  1318.                         quit
  1319.                     endif
  1320.                     if next entry not same as this entry
  1321.                         break from inner do loop
  1322.                     endif
  1323.                     print current element without its name at far left
  1324.                     point at next element in chain
  1325.                 end do
  1326.             endif
  1327.             point at next element in chain
  1328.         end do
  1329.     */
  1330.     struct prog_queue_struct *ptr;
  1331.     int onpath_char;
  1332.     ptr=prog_queue;
  1333.     for(;;){
  1334.         if(!ptr)return;
  1335.         if(show_all_files||
  1336.         (ptr->next&&
  1337.         ptr->progname==ptr->next->progname&&
  1338.         (!got_dir_specs||ptr->dir_queue_ptr->on_search_path))){
  1339.             if(!showed_something){
  1340.                 showed_something=1;
  1341.                 MPRINTF("%s",aacReptHdr[0]);
  1342.                 MPRINTF("%s",aacReptHdr[1]);
  1343.                 MPRINTF("%s",
  1344.                 show_nonpath&&got_dir_specs?aacReptHdr[2]:"");
  1345.                 MPRINTF("%s",aacReptHdr[0]);
  1346.             }
  1347.             onpath_char=
  1348.             ptr->dir_queue_ptr->on_search_path?IS_ON:IS_OFF;
  1349.             if(!got_dir_specs)onpath_char=IS_ON;
  1350.             MPRINTF("%8s%c%s%s%s\n",ptr->progname,onpath_char,
  1351.             ptr->dir_queue_ptr->dirname,
  1352.             ptr->progname,comexebat[ptr->ext_type]);
  1353.             for(;;){
  1354.                 if(!ptr->next)return;
  1355.                 if(ptr->progname!=ptr->next->progname)break;
  1356.                 ptr=ptr->next;
  1357.                 onpath_char=
  1358.                 ptr->dir_queue_ptr->on_search_path?IS_ON:IS_OFF;
  1359.                 if(!got_dir_specs)onpath_char=IS_ON;
  1360.                 MPRINTF("%8s%c%s%s%s\n","",onpath_char,
  1361.                 ptr->dir_queue_ptr->dirname,
  1362.                 ptr->progname,comexebat[ptr->ext_type]);
  1363.             }
  1364.         }
  1365.         ptr=ptr->next;
  1366.     }
  1367. }
  1368. void show_big_results(void){
  1369. /** void show_big_results(void);
  1370.         If show_all_files is zero
  1371.             Displays the the big_prog_queue entries, if any conflicts.
  1372.             If not, displays a 'no conflicts found' message.
  1373.         else
  1374.             displays all big_prog_queue entries, if there are any.
  1375.             if not, displays a 'no files found' message.
  1376.         endif
  1377.         this function is exactly like show_results, but uses
  1378.             big_prog_queue instead of prog_queue, and prints out
  1379.             file dates, times and sizes.
  1380. **/
  1381.     /* pseudocode
  1382.         if off end of list
  1383.             return
  1384.         endif
  1385.         do forever
  1386.             if no entry after this
  1387.                 quit
  1388.             endif
  1389.             if (showing all files) or
  1390.             (this progname matches next progname)
  1391.                 if showed_something is zero
  1392.                     showed_something=1
  1393.                     print header
  1394.                 endif
  1395.                 print this entry with basename in far left of line
  1396.                 do forever
  1397.                     if off end of list
  1398.                         quit
  1399.                     endif
  1400.                     if next entry not same as this entry
  1401.                         break from inner do loop
  1402.                     endif
  1403.                     print current element without name at far left
  1404.                     point at next element in chain
  1405.                 end do
  1406.             endif
  1407.             point at next element in chain
  1408.         end do
  1409.     */
  1410.     struct big_prog_queue_struct *ptr;
  1411.     char out_array[200];                /* plenty big */
  1412.     ptr=big_prog_queue;
  1413.     for(;;){
  1414.         if(!ptr)return;
  1415.         if(show_all_files||
  1416.         (ptr->next&&
  1417.         ptr->progname==ptr->next->progname&&
  1418.         (!got_dir_specs||ptr->dir_queue_ptr->on_search_path))){
  1419.             if(!showed_something){
  1420.                 showed_something=1;
  1421.                 MPRINTF("%s",aacReptHdr[0]);
  1422.                 MPRINTF("%s",aacReptHdr[1]);
  1423.                 MPRINTF("%s",
  1424.                 show_nonpath&&got_dir_specs?aacReptHdr[2]:"");
  1425.                 MPRINTF("%s",aacReptHdr[0]);
  1426.             }
  1427.             make_big_output(out_array,ptr->progname,ptr);
  1428.             MPRINTF("%s\n",out_array);
  1429.             for(;;){
  1430.                 if(!ptr->next)return;
  1431.                 if(ptr->progname!=ptr->next->progname)break;
  1432.                 ptr=ptr->next;
  1433.                 make_big_output(out_array,"",ptr);
  1434.                 MPRINTF("%s\n",out_array);
  1435.             }
  1436.         }
  1437.         ptr=ptr->next;
  1438.     }
  1439. }
  1440. void make_big_output(char *out_array,char *label,
  1441. struct big_prog_queue_struct *ptr){
  1442. /** void make_big_output(char *out_array,char *label,
  1443.     struct big_prog_queue_struct *ptr);
  1444.     this takes info about a file to print out, and tries to make it 79
  1445.     chars long.  if it must be longer, ensures a space before file
  1446.     size and date.
  1447. **/
  1448.     char *temp_ptr=out_array;
  1449.     int chars_to_go=53,am_pm,onpath_char;
  1450.     unsigned int hour,minute,day,month,year;
  1451.     hour=ptr->ft.time_bits.ft_hour;
  1452.     minute=ptr->ft.time_bits.ft_min;
  1453.     day=ptr->fd.date_bits.fd_day;
  1454.     month=ptr->fd.date_bits.fd_month;
  1455.     year=ptr->fd.date_bits.fd_year;
  1456.     /*
  1457.     **  set am/pm
  1458.     */
  1459.     am_pm=ptr->ft.time_bits.ft_hour>=12?'p':'a';
  1460.     /*
  1461.     **  normalize hour
  1462.     */
  1463.     if(!hour)hour=12;
  1464.     else if(hour>12)hour-=12;
  1465.     /*
  1466.     **  normalize year
  1467.     */
  1468.     year+=1980;
  1469.     year%=100;                                /* get rid of century */
  1470.     /*
  1471.     **  set onpath_char
  1472.     */
  1473.     onpath_char=ptr->dir_queue_ptr->on_search_path?IS_ON:IS_OFF;
  1474.     if(!got_dir_specs)onpath_char=IS_ON;
  1475.     /*
  1476.     **  print out text data
  1477.     */
  1478.     sprintf(out_array,"%8s%c%s%s%s",label,onpath_char,
  1479.     ptr->dir_queue_ptr->dirname,
  1480.     ptr->progname,comexebat[ptr->ext_type]);
  1481.     /*
  1482.     **  append spaces if desired
  1483.     */
  1484.     while(*temp_ptr){temp_ptr++;chars_to_go--;};
  1485.     *temp_ptr++=' ';                         /* guarantee one space */
  1486.     while(chars_to_go>0){*temp_ptr++=' ';chars_to_go--;}/*blank fill*/
  1487.     /*
  1488.     **  write file size, date, etc.
  1489.     */
  1490.     sprintf(temp_ptr,"%7ld  %2u-%02u-%02u  %2u:%02u%c",
  1491.     ptr->ff_fsize,month,day,year,hour,minute,am_pm);
  1492. }
  1493. void check_screen_overflow(void){
  1494. /** void check_screen_overflow(void);
  1495.     this function simply notes whether the screen is filled up, and
  1496.     forces the user to press a key before continuing if so, if
  1497.     screenful_pause is nozero.
  1498. **/
  1499.     if(!screenful_pause)return;
  1500.     if(!lines_left_on_screen){
  1501.         lines_left_on_screen=SCREEN_LINES;
  1502.         fprintf(stderr,"Press a key to continue, ESC for nonstop\n");
  1503.         if(getch()==27)screenful_pause=0;
  1504.         return;
  1505.     }
  1506.     lines_left_on_screen--;
  1507. }
  1508. void *malloc_nofree(size_t size){
  1509. /** void *malloc_nofree(size_t size);
  1510.         malloc() allows you to free any allocation you make. this is
  1511.     great, but if you're allocating a lot of small amounts of memory,
  1512.     malloc's housekeeping information can take up more space than
  1513.     your data!
  1514.         This lean front end to malloc() allocates MALLOC_BLOCK_SIZE
  1515.     bytes at a time, and only keeps track of how much is left to
  1516.     give out and its address.  This means you can't free() anything
  1517.     you get from malloc_nofree() -- hence the name.
  1518.         It is not considered good form to allocate memory and then
  1519.     allow your program to quit without freeing it. Since DOS does in
  1520.     fact clean up, there's no real need to manually free all the
  1521.     blocks we get from malloc().  If you port this to another system,
  1522.     you might have to implement a method of freeing the memory blocks
  1523.     you get from malloc().
  1524. **/
  1525.     static void *current_block=(void *)0;
  1526.     static size_t on_hand=0;
  1527.     static int try_malloc=1;
  1528.     void *ret_ptr;
  1529.     if(size>MALLOC_BLOCK_SIZE){
  1530.         #ifdef DEBUG
  1531.             ret_ptr=malloc(size);
  1532.             if(ret_ptr)total_malloc_memory+=(unsigned long)size;
  1533.             return ret_ptr;
  1534.         #else
  1535.             return malloc(size);
  1536.         #endif
  1537.     }
  1538.     if(on_hand<size){             /* forget leftover stub, malloc() */
  1539.         if(try_malloc){
  1540.             current_block=malloc(MALLOC_BLOCK_SIZE);
  1541.             #ifdef DEBUG
  1542.                 total_memory_wasted+=(unsigned long)on_hand;
  1543.                 if(current_block){
  1544.                     total_malloc_memory+=MALLOC_BLOCK_SIZE;
  1545.                 }
  1546.             #endif
  1547.             on_hand=current_block?MALLOC_BLOCK_SIZE:0;
  1548.             if(!on_hand)try_malloc=0;
  1549.         }
  1550.     }
  1551.     if(on_hand>=size){
  1552.         ret_ptr=current_block;
  1553.         on_hand-=size;
  1554.         current_block=&((char *)current_block)[size];
  1555.         #ifdef DEBUG
  1556.             total_memory_used+=(unsigned long)size;
  1557.         #endif
  1558.         return ret_ptr;
  1559.     }
  1560.     return malloc(size);
  1561. }
  1562. int cur_drive_is_subst(void){
  1563. /** int cur_drive_is_subst(void);
  1564.         works by getting the current working directory, then
  1565.             performing get_truename() on it.  if get_truename()
  1566.             returns nonzero (it got a different answer), we are
  1567.             on a SUBST drive.  Used this instead of int 21h, 1Fh
  1568.             since 1Fh is undocumented (in MS-DOS Encyclopedia,
  1569.             anyway) and we already need the TRUENAME function.
  1570.             Why introduce more undocumented functions than is
  1571.             absolutely necessary?
  1572.         returns 1 yes, current drive is SUBST drive
  1573.                 0 no, current drive is not SUBST
  1574.         always returns 0 if !dos_has_subst
  1575. **/     
  1576.     char buffer[MAXPATH+15],true[MAXPATH+15],*ptr;
  1577.     if(!dos_has_subst)return 0;
  1578.     if(!getcwd(buffer,sizeof(buffer)-1))EXIT(ERROR_DOS)
  1579.     ptr=buffer;
  1580.     while(*ptr)ptr++;
  1581.     if(ptr[-1]!='\\'){                  /* ensure ends in backslash */
  1582.         *ptr='\\';
  1583.         ptr[1]=0;
  1584.     }
  1585.     return get_truename(true,buffer);
  1586. }
  1587. int get_truename(char *true,char *original){
  1588. /** int get_truename(char *true,char *original);
  1589.         tries to get the true name corresponding to the name in
  1590.             *original. ensures the new name ends with '\\'
  1591.         returns 1 yes got a new name and it's not the same as the
  1592.                   old one
  1593.                 0 no, didn't get a new name or !dos_has_subst or it
  1594.                   was the same as the old
  1595. **/
  1596.     char *ptr;
  1597.     union REGS inregs,outregs;
  1598.     struct SREGS segregs;
  1599.     if(!dos_has_subst)return 0;
  1600.     inregs.x.ax=0x6000;
  1601.     /*lint -e10 -e67 -e507 lint chokes on Borland macros */
  1602.     segregs.ds=FP_SEG(original);
  1603.     inregs.x.si=FP_OFF(original);
  1604.     segregs.es=FP_SEG(true);
  1605.     inregs.x.di=FP_OFF(true);
  1606.     /*lint +e10 +e67 +e507*/
  1607.     int86x(0x21,&inregs,&outregs,&segregs);
  1608.     ptr=true;
  1609.     if(!*ptr)return 0;
  1610.     while(*ptr)ptr++;
  1611.     if(ptr[-1]!='\\'){                  /* ensure ends in backslash */
  1612.         *ptr='\\';
  1613.         ptr[1]=0;
  1614.     }
  1615.     return strcmp(original,true)?1:0;
  1616. }
  1617. int does_dos_have_subst(void){
  1618. /** int does_dos_have_subst(void);
  1619.         since DOS did not supply SUBST until version 3.1 (MS-DOS
  1620.             Encyclopedia, Microsoft, p. 938), we don't
  1621.             have to worry about it until then.
  1622.         returns 1 for DOS 3.1 or greater
  1623.                 0 otherwise
  1624. **/
  1625.     union REGS inregs,outregs;
  1626.     struct SREGS segregs;
  1627.     inregs.h.ah=0x30;
  1628.     inregs.h.al=0x00;
  1629.     int86x(0x21,&inregs,&outregs,&segregs);
  1630.     #ifdef DEBUG
  1631.         printf("DOS version is %d.%02d\n",
  1632.         (int)outregs.h.al,(int)outregs.h.ah);
  1633.     #endif
  1634.     if(outregs.h.al<3)return 0;                      /* 0.00 - 2.99 */
  1635.     if((outregs.h.al==3)&&(outregs.h.ah<10))return 0;  /* 3.00-3.09 */
  1636.     return 1;                                             /* 3.10 + */
  1637. }
  1638. #ifdef __ZTC__
  1639. int getdisk(void){
  1640. /** int getdisk(void);
  1641.     returns the number for the current disk drive, A=1, B=2, etc.
  1642. **/
  1643.     unsigned int retval;
  1644.     dos_getdrive(&retval);
  1645.     return (int)retval;
  1646. }
  1647. #endif
  1648.  
  1649.